//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBind.h>

#include "fixtures/SquirrelFixture.h"
#include "mocks/MockNativeFunction.h"
#include "TypeHelpers.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture BindNativeSingletonFunctionTest;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindNativeSingletonFunctionTest, TestNoOptions)
{
  ::testing::StrictMock<MockNativeFunction> mock;
  MockNativeFunction::m_instance = &mock;

  EXPECT_CALL(mock, NativeClassFunction(m_vm))
    .Times(3);

  // test wrong object on the stack fails
  //
  EXPECT_FALSE(sqb::Bind::BindNativeSingletonFunction(m_vm, 1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("native")));

  sq_pushroottable(m_vm);

  // test null instance pointer fails
  //
  EXPECT_FALSE(sqb::Bind::BindNativeSingletonFunction(m_vm, 1, static_cast<MockNativeFunction*>(nullptr), &MockNativeFunction::NativeClassFunction, _SC("native")));

  // test invalid name fails
  //
  EXPECT_FALSE(sqb::Bind::BindNativeSingletonFunction(m_vm, 1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, nullptr));
  EXPECT_FALSE(sqb::Bind::BindNativeSingletonFunction(m_vm, 1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("")));

  // test valid arguments succeed
  //
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, 1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("positive")));
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("negative")));

  CompileAndSucceedCall(_SC("positive()"));
  CompileAndSucceedCall(_SC("negative()"));

  // test binding to a class
  //
  sq_pushstring(m_vm, _SC("klass"), -1);

  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("native")));

  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));

  CompileAndSucceedCall(_SC("klass.native()"));

  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindNativeSingletonFunctionTest, TestParamCheck)
{
  sq_pushroottable(m_vm);

  ::testing::StrictMock<MockNativeFunction> mock;
  MockNativeFunction::m_instance = &mock;

  // test invalid typemask fails
  //
  {
    sqb::FunctionOptions options;
    options.ParamCheckCount(5);
    options.TypeMask(_SC("hello"));
    EXPECT_FALSE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("test"), options));
  }

  EXPECT_CALL(mock, NativeClassFunction(m_vm))
    .Times(3);

  // test half valid typemask does not set
  //
  {
    sqb::FunctionOptions options;
    options.ParamCheckCount(3);
    options.TypeMask(nullptr);
    ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("test"), options));
  }
  CompileAndSucceedCall(_SC("test()"));

  {
    sqb::FunctionOptions options;
    options.TypeMask(_SC(".s"));
    ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("test"), options));
  }

  CompileAndSucceedCall(_SC("test()"));

  // test valid typemask succeeds
  //
  {
    sqb::FunctionOptions options;
    options.ParamCheckCount(2);
    options.TypeMask(_SC(".s"));
    ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -1, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("native"), options));
  }

  CompileAndFailCall(_SC("native()"));
  CompileAndSucceedCall(_SC("native(\"string\")"));

  // pop roottable off stack
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindNativeSingletonFunctionTest, TestFreeVariables)
{
  ::testing::StrictMock<MockNativeFunction> mock;
  MockNativeFunction::m_instance = &mock;

  sq_pushroottable(m_vm);

  sqb::FunctionOptions options;
  options.FreeVariables(2);

  // free variables are bound backwards so the last in the stack will be the first free variable for the function.
  //
  sq_pushstring(m_vm, kExpectedString, -1);
  sq_pushinteger(m_vm, kExpectedInteger);
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction(m_vm, -3, static_cast<MockNativeFunction*>(&mock), &MockNativeFunction::NativeClassFunction, _SC("native"), options));

  sq_pushstring(m_vm, _SC("native"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));

  SQUnsignedInteger parameterCount = 0;
  SQUnsignedInteger freeVariableCount = 0;
  EXPECT_SQ_SUCCEEDED(m_vm, sq_getclosureinfo(m_vm, -1, &parameterCount, &freeVariableCount));

  EXPECT_EQ(0, parameterCount);
  EXPECT_GE(freeVariableCount, 2u);

  sq_pop(m_vm, 2);
}
